/**
 * 
 */
package function;

import java.util.ArrayList;
import java.util.List;

import multiarray.MultiArray;
import solver.Results;
import ccpv.Person;
import ccpv.RunTimeSettings;

/**
 * @author robert
 *
 */
public class ConflictFunction2 {
	private static double lambda = 0.5;
	private static  double I = 0.5;
	private static  double c = 0.2;
	private static  double f = 0.1;
	private static  double precision = 1E-99;
	private double[] PI;

	private int num_coalitions = 0;
	private List<List<int[]>> coalitions;
	private MultiArray<Person> population;
	private boolean isPopulationSet = false;
	private boolean isCoalitionsSet = false;
	
	private static double epsilon = 1.0E-80;
	
	private double[][] thetas;
	private boolean isThetaSet = false;
	private double[] utility;
	private boolean isUtilityCalculated = false;
	
	
	public double f(double[][] thetas, int i) {
		setTheta(thetas);
		double result = 0.0;
		result = ubar(this.thetas, i);
		for (int j = 0; j<num_coalitions; j++) {
			if (i==j) continue;
			result +=  beta(coalitions, population, i, j)*ubar(thetas,j);
		}
		return result;	
		}

	public double expected_payoff(double[][] thetas, int i) {
		return 0;
	}
	
	public double utility(double[][] thetas, int i) {
		return 0;
	}
	
	public double f(int i) {
		if (!isThetaSet) return -1;
		double result = 0.0;
		result = ubar(this.thetas, i);
		for (int j = 0; j<num_coalitions; j++) {
			if (i==j) continue;
			result +=  beta(coalitions, population, i, j)*ubar(thetas,j);
		}
		return result;	
		}
	

	public int getNumCoalitions() {
		return num_coalitions;
	}

	public double[] copyArray(double[] in) {
		double[] out = new double[in.length];
		for (int i = 0; i<in.length; i++) {
			out[i] = in[i];
		}
		return out;
	}
	public double[] calcUtilities() {
		if (!(isPopulationSet && isCoalitionsSet)) return null;
		if (isUtilityCalculated) return copyArray(utility);
		
		double[] utility = new double[num_coalitions];
		for (int i = 0; i<num_coalitions; i++) {
			utility[i] = f(i);
			this.utility[i] = utility[i];
		}
		isUtilityCalculated = true;
		return utility;
	}
	
	public void setTheta(double[][] theta) {
		this.thetas = new double[theta.length][theta.length];
		for (int i = 0; i<theta.length; i++) {
			for (int j = 0; j<theta.length; j++) {
				this.thetas[i][j] = theta[i][j];
			}	
		}
		isThetaSet = true;
		isUtilityCalculated = false;
	}
	
	public double[] getBounds() {
		double[] PIc = new double[PI.length];
		for (int i=0; i<PI.length; i++) {
			PIc[i] = PI[i];
		}
		return PIc;
	}

	

	public double jacobian(double[][] thetas, int i, int m, int n) {
		double beta = 0.0;
		double sum = 0.0;
		
		sum = ubar_deriv (thetas, i, m, n);
		
		double term = 0.0;
		for (int j = 0; j<thetas.length; j++) {
			if (i == j) continue;
			//beta = beta(coalitions, population, i, j);
			//TODO: use Leo's beta
            term = ubar_deriv (thetas, j, m, n);
			sum += beta * term;
		}
		
		return sum;
	}
	

	
	private double pibar(double[][] thetas, int i) {
		return (1-c)*PI[i]+c*thetas[i][i];
	}
	
	private double ubar(double[][] thetas, int i) {
	  double sum = 0.0;
	  for (int j = 0; j<num_coalitions; j++) {
		  if (i == j) continue;
		  sum += numerator(thetas, i,j)/denominator(thetas,i,j); 
	  }
	  sum = sum *(lambda/(num_coalitions-1));
	  sum += pibar(thetas,i);
	  sum = sum *(1-I);
	  sum += I*PI[i];
	  return sum;
	}
	
	/**
	 * Calculates the derivative of U^bar with respect to Theta_m,n
	 * @param thetas
	 * @param i
	 * @param j
	 * @param m
	 * @param n
	 * @return
	 */
	public double ubar_deriv (double[][] thetas, int i, int m, int n) {
		if ((i == m) && (m == n)) {
			return iii_deriv(thetas,i);
		}
		if ((i==m)&&(i != n)&&(m != n)) {
			return iij_deriv(thetas,i,n);
		}
		if ((i != m)&&(m==n)) {
			return jii_deriv(thetas,i,m);
		}
		if ((i!=m)&&(m!=n)&&(i==n)) {
		    return ijk_deriv(thetas,i,m,n);	
		}
		return 0;
	}
	
	private double ijk_deriv(double[][] thetas, int j, int i, int k) {
		double value = 0.0;
		double denom = denominator(thetas,i,k);
		value = epsilon*pibar(thetas,k) + thetas[k][i]*(pibar(thetas,k)+pibar(thetas,i));
		value = value / (denom*denom);
		return value*-1*(1-I)*(lambda/(num_coalitions-1));
	}

	private double jii_deriv(double[][] thetas, int j, int i) {
		double value = 0.0;
		value = c*thetas[j][i]/denominator(thetas,j,i);
		return value*(1-I)*(lambda/(thetas.length-1));
	}

	private double iij_deriv(double[][] thetas, int i, int j) {
		double value = 0.0;
		double denom = denominator(thetas,i,j);
		value = epsilon*pibar(thetas,j)+thetas[j][i]*(pibar(thetas,j)+pibar(thetas,i));
		value = value / (denom*denom);
		return value*(1-I)*(lambda/(thetas.length-1));
	}

	private double iii_deriv(double[][] thetas, int i) {
		double value = 0.0;
		for (int j=0; j<thetas.length; j++) {
			if (i == j) continue; // skip this iteration
			value = value + (thetas[j][i])/(denominator(thetas,i,j));
		}
		value = value *(-1*lambda/(num_coalitions-1));
		value = value + 1;
		return (1-I)*c*value;
	}

	private static double denominator(double[][] thetas, int i, int j) {
		return epsilon + thetas[i][j]+thetas[j][i];
	}

	public double numerator(double[][] thetas, int i, int j) {
		return thetas[i][j]*pibar(thetas, j)-thetas[j][i]*pibar(thetas, i);
	}

	public double numeratorDeriv(double[][] thetas, int i) {
		return (1-c)*PI[i]+c*thetas[i][i];
	}
	
	public void setPopulation(MultiArray<Person> population) {
		this.population = population;
		isPopulationSet = true;
		if (isCoalitionsSet) {
			initializeVariables();
		}
		isThetaSet = false;
		isUtilityCalculated = false;
	}

	private static List<List<int[]>> copyListListIntArray(List<List<int[]>> in) {
		List<List<int[]>> out = new ArrayList<List<int[]>>();
		for(int i = 0; i<in.size(); i++) {
			out.add(new ArrayList<int[]>());
			for (int j=0; j<in.get(i).size(); j++) {
				out.get(i).add(in.get(i).get(j));
			}
		}
		return out;
	}
	
	public void printCoalitions() {
		System.out.println("Num coalitions:" + coalitions.size());
		System.out.println("Coalitions:" + coalitions.size());
		for (int i = 0; i < this.coalitions.size(); i++) {
			for (int j = 0; j<this.coalitions.get(i).size(); j++) {
				System.out.print("  ");
				for (int k = 0; k<this.coalitions.get(0).get(0).length; k++) {
					System.out.print(this.coalitions.get(i).get(j)[k]);
				}
				System.out.println();
			}
			System.out.println();
		}
	}
	
	public void setCoalitions(List<List<int[]>> coalitions) {
		isCoalitionsSet = true;
		//dims = coalitions.get(0).get(0).length;
		num_coalitions = coalitions.size(); 
		PI = new double[coalitions.size()];

		this.coalitions = copyListListIntArray(coalitions);
				
		if (isPopulationSet) {
			initializeVariables();
		}
		isThetaSet = false;
		isUtilityCalculated = false;
	}
	
	private void initializeVariables() {
		if (!(isPopulationSet && isCoalitionsSet)) return;
		double total_power = 0;
		Person temp_person;
		int[] tempChar = new int[3];
		double tempPower = 0;
		for (int i = 0; i<coalitions.size(); i++) {
			for (int j = 0; j<coalitions.get(i).size(); j++) {
				tempChar = coalitions.get(i).get(j);
				System.out.print("Getting person ");
				for (int k = 0; k<coalitions.get(i).get(j).length; k++) {
					System.out.print(coalitions.get(i).get(j)[k]);
				}
				System.out.println();
				temp_person = population.getElementAtIndex(tempChar); 
				System.out.println("  Power: " +temp_person.getPower() + "  Population: " + temp_person.getQty());
				tempPower = temp_person.getPower()*temp_person.getQty();
				PI[i] = PI[i] + tempPower;
				total_power = total_power + tempPower;  
			}
		 }	
		isUtilityCalculated = false;
	}
	
	/**
	 * Initializes the constants in the function, stored in runtimesettings.
	 * @param rt
	 */
	public void initializeConstants(RunTimeSettings rt) {
		lambda = rt.getLambda();
		c = rt.getC();
		I = rt.getI();
		f = rt.getF();
		setPrecision(rt.getPrecision());
		isUtilityCalculated = false;
	}
	
	public double beta(List<List<int[]>> coalitions2, MultiArray<Person> population, int i, int j) {
		// TODO: use leo's beta function instead
		double[] beta_i = new double[coalitions2.get(i).size()];
		for (int m = 0; m<coalitions2.get(i).size(); m++) {
			// for each group in source coalition, find average 
			for (int n = 0; n<coalitions2.get(j).size(); n++) { 
				beta_i[m] += population.getElementAtIndex(coalitions2.get(i).get(m)).getAltruism(coalitions2.get(j).get(n));
			}
			beta_i[m] = beta_i[m] / coalitions2.get(j).size();
		}
		double sum = 0.0;
		for (int m = 0; m<beta_i.length; m++) {
			sum += beta_i[m];
		}
		sum = sum / beta_i.length;
		return sum;
	}

	
	public double getI() {
		return I;
	}

	
	public double getF() {
		return f;
	}

	/**
	 * @param precision the precision to set
	 */
	public void setPrecision(double precision) {
		this.precision = precision;
	}

	/**
	 * @return the precision
	 */
	public double getPrecision() {
		return precision;
	}


	public void updateAgents(Results res) {
		setTheta(res.getThetasD());
		
		if (!res.isConflict) return;
		double[] sum = new double[num_coalitions];
		double temp = 0.0;
		// expenditure function
		for (int i = 0; i < num_coalitions; i++) {
			sum[i] = pibar(thetas, i);
		}
		
		for (int i = 0; i< num_coalitions; i++) {
			for (int j = i; j< num_coalitions; j++) {
				if (i==j) continue;
			    if (res.isWinner(i, j)) {
			    	temp = winloseValue(j);
			    	sum[i] += temp;
			    	sum[j] -= temp;
			    } else {
			    	temp = winloseValue(i);
			    	sum[i] -= temp;
			    	sum[j] += temp;
			    }
			}		
		}
	for (int i = 0; i<num_coalitions; i++){
		// distribute spoils
		distributePower(i,sum[i]);
 	}

		incrementPower();	
		
	}
	
	private void distributePower(int coal, double power) {
		// calculate the total number of people
		int count = 0;
		for (int[] guy: coalitions.get(coal)) {
			count += population.getElementAtIndex(guy).getQty();
		}
		double fraction = power / count; // power going back to each person
		
		// redistribute based upon the number of people
		Person p;
		for (int[] guy: coalitions.get(coal)) {
			p = population.getElementAtIndex(guy);
			//p.setPower(p.getQty()*fraction); // for individual agents
			p.setPower(fraction); // for group agents
		}
	}
	
	private void incrementPower() {
		Person p;
		for (List<int[]> coal: coalitions) {
			for (int[] guy: coal) {
				p = population.getElementAtIndex(guy);
				if (p.isPowerless()) {
					p.revertPower(1+f); // sociologists might not like this
				} else {
					p.setPower(p.getPower()*(1+f));
				}
			}
		}
	}
	
	private double winloseValue(int i) {
		 //Lambda * Pibar(j)/(L-1)
		return lambda*pibar(thetas,i)/(num_coalitions-1);
	}



	
}
